Kealdish's Studio.

UICollectionView Layout学习笔记

字数统计: 1.5k阅读时长: 5 min
2015/12/17 Share

概览

UITableViewUIKit视图类中极其重要的一个,我们与之打交道的频率也是极高的。在UIKit视图类中有一个类与UITableview共享一套API设计,但相比UITableview,该类的视图布局更加灵活复杂。这就是UICollectionView。本篇的着重点也放在UICollectionView灵活强大的视图布局上面。

UICollectionView结构

UICollectionView的结构可以分为两部分来看:

从数据和驱动角度能看到两部分:

  • dataSource:数据源,遵循UICollectionViewDataSource协议,负责向collectionView提供数据以及视图。同时,还负责处理cells、supplementary views的创建和配置工作。
  • delegate:代理,遵循UICollectionViewDelegate协议,负责管理item视图的选中高亮状态以及item的点击事件。

从视图角度能看到三部分:

  • Cells:在collection view的可视范围内负责展示数据的内容
  • Supplementary views:也是负责展示数据,但与cells不同,它无法被用户选中,类似于UITableview的header和footer
  • Decoration views:属于装饰性view,不与collectionView的数据产生联系,更像是另一类的Supplementary views。
    注:Supplementary views、Decoration views必须是UICollectionReusableView的子类。

这些组成结构与UITableview比较,并没有太大的区别,真正的区别在于UICollectionLayout,这不仅是本篇笔记的着重点,也是UICollectionView的精髓。

UICollectionViewLayout

UICollectionViewLayout是一个抽象类,因此我们必须要继承并实现相关方法才能使用它。Layout的工作是确定Cells、supplementary views、decoration views在collectionView的bounds中的位置并且当collectionView需要时传递相关信息。collectionView根据传递过来的相关layout信息对views进行相应和处理以便能让他们在屏幕上能够显示出来。

UICollectionViewFlowLayout是苹果提供的继承于UICollectionViewLayout的子类。当你对layout的需求不是很复杂的时候,UICollectionViewFlowLayout往往可以满足需求,并且实现也比较简单,这里就不再讨论,感兴趣的可以查阅相关文档UICollectionviewFlowLayout

自定义继承UICollectionViewLayout的子类必须重写以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)prepareLayout
// collectionView会在第一次布局向layout对象发送第一条消息时调用一次该方法,或者在invalidaed方法调用后在访问布局信息之前会再次调用该方法,重写该方法做些初始化的工作以确保layout实例的正确,子类重写必须调用super方法。
- (CGSize)collectionViewContentSize
// 返回collectionView的内容的尺寸。collectionView对它的content并不知情,因此需要提供滚动区域大小才能正确滚动。滚动区域大小必须包含内容的总大小,包括supplementary views 和 decoration views。
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
// 返回rect中的所有的元素的布局属性,其是包含UICollectionViewLayoutAttributes的数组。该方法传递一个自身坐标系的矩形进来,这个矩形也就是collectionView的bounds。这个方法涉及到所有类型的视图,即包括cells、supplementary views和decoration views。
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath )indexPath
// 返回对应于indexPath的位置的cell的布局属性。有时候collectionView会为某个特殊的视图向layout对象请求布局属性。你可以通过调用 +[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:]这个方法,然后根据 index path 修改属性。为了得到需要显示在这个 index path 内的数据,你可能需要访问 collection view 的数据源。到目前为止,至少确保设置了 frame 属性,除非你所有的 cell 都位于彼此上方。
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString )kind atIndexPath:(NSIndexPath *)indexPath
// (可选)返回对应于indexPath的位置的追加视图的布局属性,如果没有追加视图可不重载
- (UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString)decorationViewKind atIndexPath:(NSIndexPath )indexPath
// (可选)返回对应于indexPath的位置的装饰视图的布局属性,如果没有装饰视图可不重载
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
// 当collectionView的bounds发生改变时,是否应该重新布局。如果YES则在边界变化(一般是scroll到其他地方)时,将重新计算需要的布局信息。

这些方法提供了collectionView在屏幕上展示内容所需要的基本布局信息。当collectionView中的数据发生改变并且items需要执行增删操作,collectionView会要求layout对象去更新layout布局信息。任何item被添加、移除、移动都必须更新它的布局信息来对应它正确的位置信息。当item被移动时,collectionView调用上面的方法来重新获取item更新后的布局属性,而item被插入或删除时,collectionView会调用不同的方法,也就是你需要重写的方法:

1
2
3
4
5
6
7
8
9
10
11
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath;
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)decorationIndexPath;
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingSupplementaryElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath;
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath;
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)decorationIndexPath;
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingSupplementaryElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath;

此外,你也可以重写prepareForCollectionViewUpdates:方法来处理任何与布局相关的预处理操作,重写finalizeCollectionViewUpdates方法来添加动画或者实现布局相关的任务。

UICollectionViewLayoutAttributes

重写的方法中涉及到一个类UICollectionViewLayoutAttributes。该类负责管理collectionView中给定item的布局相关的属性。当collectionView需要时,便会要求layout对象创建该类的实例对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@interface UICollectionViewLayoutAttributes : NSObject <NSCopying, UIDynamicItem>
@property (nonatomic) CGRect frame;
@property (nonatomic) CGPoint center;
@property (nonatomic) CGSize size;
@property (nonatomic) CATransform3D transform3D;
@property (nonatomic) CGRect bounds NS_AVAILABLE_IOS(7_0);
@property (nonatomic) CGAffineTransform transform NS_AVAILABLE_IOS(7_0);
@property (nonatomic) CGFloat alpha;
@property (nonatomic) NSInteger zIndex; // default is 0
@property (nonatomic, getter=isHidden) BOOL hidden; // As an optimization, UICollectionView might not create a view for items whose hidden attribute is YES
@property (nonatomic, strong) NSIndexPath *indexPath;
@property (nonatomic, readonly) UICollectionElementCategory representedElementCategory;
@property (nonatomic, readonly, nullable) NSString *representedElementKind; // nil when representedElementCategory is UICollectionElementCategoryCell
+ (instancetype)layoutAttributesForCellWithIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind withIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind withIndexPath:(NSIndexPath *)indexPath;
@end

可以看出,UICollectionViewLayoutAttributes包含了Cell的frame、center、bounds等布局信息。

结语

相比于UITableviewUICollectionview布局的复杂程度要更复杂得多,能很好的写出一个很不容易。正是由于UICollectionview布局的复杂性,我们更需要多去使用,在使用的过程中才能更好的理解UICollectionview本身这套API,也更好地学习苹果设计类的灵活性。

CATALOG
  1. 1. 概览
  2. 2. UICollectionView结构
  3. 3. UICollectionViewLayout
  4. 4. UICollectionViewLayoutAttributes
  5. 5. 结语